package  {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.display.Bitmap
import flash.ui.Keyboard
import flash.display.StageQuality

import com.google.maps.interfaces.IMapType;
import com.google.maps.LatLng;
import com.google.maps.Map;
import com.google.maps.MapEvent;
import com.google.maps.MapType;

import org.papervision3d.view.Viewport3D;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.objects.parsers.Collada;
import org.papervision3d.view.stats.StatsView;
import org.papervision3d.view.layer.ViewportLayer;
import org.papervision3d.view.layer.util.ViewportLayerSortMode;
import org.papervision3d.materials.special.FogMaterial;
import org.papervision3d.core.render.filter.FogFilter;
import org.papervision3d.events.FileLoadEvent;
import org.papervision3d.scenes.Scene3D;
import org.papervision3d.cameras.SpringCamera3D;
import org.papervision3d.render.BasicRenderEngine;
import org.papervision3d.materials.MovieMaterial;
import org.papervision3d.objects.primitives.Plane;
import org.papervision3d.objects.primitives.Cone;
import org.papervision3d.core.math.Number3D;
import org.papervision3d.core.proto.MaterialObject3D;
import org.papervision3d.materials.WireframeMaterial;

import jiglib.physics.RigidBody;
import jiglib.plugin.papervision3d.Papervision3DPhysics;
import jiglib.plugin.papervision3d.Pv3dMesh;
import jiglib.math.JNumber3D;
import jiglib.math.JMatrix3D;
import jiglib.geometry.JPlane;
import jiglib.vehicles.JCar;
import jiglib.vehicles.JWheel;
import jiglib.physics.PhysicsSystem;

import com.bit101.components.InputText;
import com.bit101.components.PushButton;
import com.bit101.components.RadioButton;

public class Main extends Sprite {
	// 3D Common
	private var viewport:Viewport3D;
	private var scene:Scene3D;
	private var camera:SpringCamera3D;
	private var renderer:BasicRenderEngine;
	
	// 3D Maps
	private var gmaps:Map;
	private var mapContainer:Sprite;
	private var mapMat:MovieMaterial;
	private var mapPlane:DisplayObject3D;
	
	// maps 3d transformations
	private var startPlane:Plane;	
	private var prevDistX:Number = 0;
	private var prevDistZ:Number = 0;
	
	private var cameraTarget:DisplayObject3D;
	
	// Physics
	private var physics:Papervision3DPhysics;
	private var carBody:JCar;
	private var carSkin:Collada;
	private var steerFR :DisplayObject3D;
	private var steerFL :DisplayObject3D;
	private var wheelFR :DisplayObject3D;
	private var wheelFL :DisplayObject3D;
	private var wheelBR :DisplayObject3D;
	private var wheelBL :DisplayObject3D;
	
	// UI
	[Embed(source = "../assets/pixel_world_map.png")] private var worldMapAsset:Class;
	private var worldMap:Bitmap = new worldMapAsset();
	
	[Embed(source = "../assets/keys2.png")]private var keysHelpAsset:Class;
	private var keysHelp:Bitmap = new keysHelpAsset();
	private var latInput:InputText;
	private var lngInput:InputText;
	private var teleportBtn:PushButton;
	private var placeButtons:Object = { };
	private var lockLatLngInputs:Boolean = false;
	
	public function Main() {
		stage.quality = StageQuality.LOW
		init3D();
		initMap();		
		addUI();
	}
	
	private function setLatLng(e:Event):void {
		var latlng:LatLng = new LatLng( Number( latInput.text ), Number( lngInput.text ) );
		gmaps.setCenter( latlng );
		lockLatLngInputs = false;
	}
	
	private function initMap():void {
		gmaps = new Map();
		gmaps.key = "";
		gmaps.setSize( new Point( 768, 768 ) ); 
		gmaps.addEventListener( MapEvent.MAP_READY, mapReadyHandler ); 
		gmaps.cacheAsBitmap = true;
		mapContainer = new Sprite();
		addChild(mapContainer);
		mapContainer.addChild( gmaps );
		
		// create plane with google maps MovieMaterial
		mapContainer.visible = false
		mapMat = new MovieMaterial(gmaps, false, true,true,new Rectangle(0,0,500,500))
		mapPlane = new Plane(mapMat, 2000, 2000, 24, 24);
		mapPlane.rotationX = 90;
		viewport.getChildLayer(mapPlane).layerIndex = 0;
		scene.addChild(mapPlane);
		
		startPlane = new Plane(new WireframeMaterial(0x77ee77), 10, 10);
		viewport.getChildLayer(startPlane).layerIndex = 0;
		scene.addChild(startPlane);

		// init physics
		physics = new Papervision3DPhysics(scene, 6);
		
		// physics ground
		var ground:RigidBody = physics.createGround(new MaterialObject3D(), 2000, 0);
		viewport.getChildLayer(physics.getMesh(ground)).layerIndex = 0;
		
		//load car
		carSkin = new Collada("res/car.xml", null, 0.2);
		carSkin.addEventListener(FileLoadEvent.LOAD_COMPLETE, onCarLoaded);
	}
	
	private function mapReadyHandler(e:Event):void {
		var type:IMapType = MapType.SATELLITE_MAP_TYPE;
		var latlng:LatLng = new LatLng( 48.873659 , 2.295764 );
		gmaps.setCenter( latlng, 17, type );
		
		this.addEventListener( Event.ENTER_FRAME, enterFrame );
		stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown)
		stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp)
	}

	private function init3D():void {
		// init papervision3d
		viewport = new Viewport3D(600, 400);
		viewport.containerSprite.sortMode = ViewportLayerSortMode.INDEX_SORT;
		scene = new Scene3D();
		this.addChild(viewport);
		
		//setup camera
		cameraTarget = new Cone(new WireframeMaterial(0xee0000), 50, 50, 1, 1);
		cameraTarget.position = new Number3D(0, 50, 0);
		camera = new SpringCamera3D(50);
		camera.mass = 10;
		camera.damping = 6;
		camera.stiffness = 3;
		camera.target = cameraTarget;
		camera.lookOffset = new Number3D(0, 20, 30);
		camera.positionOffset = new Number3D(0, 150, -270);
		renderer = new BasicRenderEngine();
		renderer.filter = new FogFilter(new FogMaterial(0x8bd6e1), 20, 100, 1200);
		
		// add stats
		addChild( new StatsView(renderer) );
	}
	
	private function onCarLoaded(e:FileLoadEvent):void{
		scene.addChild(carSkin);
		 
		//init car physics
		carBody = new JCar(new Pv3dMesh(carSkin));
		carBody.setCar(40,5,500);
		carBody.chassis.moveTo(new JNumber3D( 0, 200, -100));
		carBody.chassis.mass = 9;
		carBody.chassis.sideLengths = new JNumber3D(50, 20, 70);
		physics.addBody(carBody.chassis);
		carBody.setupWheel("wheel_fl", new JNumber3D( -20, -10, 25), 1.2, 1.5, 3, 8, 0.4, 0.5, 2);
		carBody.setupWheel("wheel_fr", new JNumber3D(20, -10, 25), 1.2, 1.5, 3, 8, 0.4, 0.5, 2);
		carBody.setupWheel("wheel_bl", new JNumber3D( -20, -10, -25), 1.2, 1.5, 3, 8, 0.4, 0.5, 2);
		carBody.setupWheel("wheel_br", new JNumber3D(20, -10, -25), 1.2, 1.5, 3, 8, 0.4, 0.5, 2);
		steerFR = carSkin.getChildByName( "Steer_FR", true );
		steerFL = carSkin.getChildByName( "Steer_FL", true );
		wheelFR = steerFR.getChildByName( "Steer_FR_PIVOT", true );
		wheelFL = steerFL.getChildByName( "Steer_FL_PIVOT", true );
		wheelBR = carSkin.getChildByName( "Wheel_RR", true );
		wheelBL = carSkin.getChildByName( "Wheel_RL", true );

		
		var vplCar:ViewportLayer = viewport.getChildLayer(carSkin);
		vplCar.addDisplayObject3D(steerFR);
		vplCar.addDisplayObject3D(steerFL);
		vplCar.addDisplayObject3D(wheelBR);
		vplCar.addDisplayObject3D(wheelBL);
		vplCar.layerIndex = 1;
	}
	
	private function updateWheelSkin():void{
		steerFL.rotationX = carBody.wheels["wheel_fl"].getSteerAngle();
		steerFR.rotationX = carBody.wheels["wheel_fr"].getSteerAngle();
		
		wheelFL.roll(carBody.wheels["wheel_fl"].getRollAngle());
		wheelFR.roll(carBody.wheels["wheel_fr"].getRollAngle());
		wheelBL.roll(carBody.wheels["wheel_bl"].getRollAngle());
		wheelBR.roll(carBody.wheels["wheel_br"].getRollAngle());
	}
		 
	
	/// LISTENERS

	private function enterFrame(e:Event):void {
		// pan google maps
		var dx:Number = carSkin.x - startPlane.x
		var dz:Number = carSkin.z - startPlane.z
		gmaps.panBy( new Point(-(prevDistX - dx), (prevDistZ-dz)));
		prevDistX = dx;
		prevDistZ = dz;
		mapPlane.copyPosition(carSkin);
		
		// update camera position 
		cameraTarget.copyTransform(carSkin);
		cameraTarget.copyPosition(carSkin); 
		
		//PhysicsSystem.getInstance().Integrate(0.1);
		physics.engine.integrate(0.12);
		updateWheelSkin();
		renderer.renderScene( scene, camera, viewport );
		
		// UI
		if(!lockLatLngInputs){
			var latlng:LatLng = gmaps.getCenter()
			latInput.text = String(latlng.lat())
			lngInput.text = String(latlng.lng())
		}
	}
	
	private function onKeyDown(e:KeyboardEvent):void {
		switch (e.keyCode){
			case Keyboard.SPACE:
				carBody.setHBrake(1);
				break;
			case "W".charCodeAt():
			case Keyboard.UP:
				carBody.setAccelerate(1);
				break;
			case "S".charCodeAt():
			case Keyboard.DOWN:
				carBody.setAccelerate(-1);
				break;
			case "A".charCodeAt():
			case Keyboard.LEFT:
				carBody.setSteer(["wheel_fl", "wheel_fr"], -1);
				break;
			case "D".charCodeAt():
			case Keyboard.RIGHT:
				carBody.setSteer(["wheel_fl", "wheel_fr"], 1);
				break;
		}
	}
	
	private function onKeyUp(e:KeyboardEvent):void {
		switch (e.keyCode){
			case Keyboard.SPACE:
				carBody.setHBrake(0);
				break;
			case "W".charCodeAt():
			case Keyboard.UP:
				carBody.setAccelerate(0);
				break;
			case "S".charCodeAt():
			case Keyboard.DOWN:
				carBody.setAccelerate(0);
				break;
			case "A".charCodeAt():
			case Keyboard.LEFT:
				carBody.setSteer(["wheel_fl", "wheel_fr"], 0);
				break;
			case "D".charCodeAt():
			case Keyboard.RIGHT:
				carBody.setSteer(["wheel_fl", "wheel_fr"], 0);
				break;
			case Keyboard.TAB:
				if ( gmaps.getCurrentMapType() == MapType.SATELLITE_MAP_TYPE ) gmaps.setMapType( MapType.NORMAL_MAP_TYPE );
				else if ( gmaps.getCurrentMapType() == MapType.NORMAL_MAP_TYPE ) gmaps.setMapType( MapType.HYBRID_MAP_TYPE );
				else if ( gmaps.getCurrentMapType() == MapType.HYBRID_MAP_TYPE ) gmaps.setMapType( MapType.PHYSICAL_MAP_TYPE );
				else if ( gmaps.getCurrentMapType() == MapType.PHYSICAL_MAP_TYPE ) gmaps.setMapType( MapType.SATELLITE_MAP_TYPE );
				break;
			case 187: // +
				gmaps.zoomIn();
				break;
			case 189: // -
				gmaps.zoomOut();
				break;
		}
	}
	
	
	//// UI

	private function addUI():void {
		latInput = new InputText(this, 300, 415, "48.873659");
		latInput.setSize(100, 18);
		lngInput = new InputText(this, 300, 439, "2.295764");
		lngInput.setSize(100, 18);
		teleportBtn = new PushButton(this, 300, 467, "Teleportate", setLatLng);
		worldMap.x = 70;
		worldMap.y = 407;
		addChild(worldMap);
		keysHelp.x = 500;
		keysHelp.y = 410;
		addChild(keysHelp);
		placeButtons["LA"] = new RadioButton(this, 80, 440, "LA", false, goLA);
		placeButtons["S"] = new RadioButton(this, 185, 450, "S", false, goS);
		placeButtons["S2"] = new RadioButton(this, 130, 430, "S", false, goS2);
		placeButtons["M"] = new RadioButton(this, 150, 430, "M", false, goM);
		placeButtons["P"] = new RadioButton(this, 140, 440, "P", true, goP);
		placeButtons["BA"] = new RadioButton(this, 114, 483, "BA", false, goBA);
		placeButtons["A"] = new RadioButton(this, 106, 445, "A", false, goA);
		placeButtons["T"] = new RadioButton(this, 195, 440, "T", false, goT);
		placeButtons["C"] = new RadioButton(this, 204, 477, "C", false, goC);
	}
	
	private function goLA(e:Event):void { latInput.text = "33.912861"; lngInput.text = "-118.180092"; lockLatLngInputs = true; }
	private function goS(e:Event):void { latInput.text = "31.120881"; lngInput.text = "121.384861"; lockLatLngInputs = true;}
	private function goS2(e:Event):void { latInput.text = "51.562878"; lngInput.text = "-1.771417"; lockLatLngInputs = true;}
	private function goM(e:Event):void { latInput.text = "55.741709"; lngInput.text = "37.654851"; lockLatLngInputs = true;}
	private function goP(e:Event):void { latInput.text = "48.873659"; lngInput.text = "2.295764"; lockLatLngInputs = true;}
	private function goBA(e:Event):void { latInput.text = "-34.603785"; lngInput.text = "-58.382069"; lockLatLngInputs = true;}
	private function goA(e:Event):void { latInput.text = "33.892019"; lngInput.text = "-84.259247"; lockLatLngInputs = true;}
	private function goT(e:Event):void { latInput.text = "35.835958"; lngInput.text = "139.8593"; lockLatLngInputs = true;}
	private function goC(e:Event):void { latInput.text = "-35.308879"; lngInput.text = "149.129502"; lockLatLngInputs = true;}
}
}